feat(analytics): anonymous LLM provider/model usage analytics#1005
Merged
feat(analytics): anonymous LLM provider/model usage analytics#1005
Conversation
Adds opt-out anonymous usage analytics that capture LLM provider and model on every real prompt dispatch and on user-initiated model changes. Reuses the existing GenieBuilder Cloudflare worker and segments DevoxxGenie traffic via app_name=devoxxgenie-intellij. The collected data guides which providers and models receive engineering investment and informs the future flat-fee LLM cloud subscription pricing. Payload is six fields plus the GA4-required envelope: client_id (UUID), session_id (10-digit per-launch), app_version, ide_version, provider_id, model_name. Never sent: prompt text, response text, conversation history, file content, file paths, project names, git remotes, API keys, credentials, token counts, cost data. Three layers of consent: a persisted notice-shown flag ensures the first-launch balloon appears exactly once per install (race-safe across concurrent project openings), an acknowledged flag gates emission until the user explicitly accepts, and an enabled flag drives the opt-out checkbox under a new Settings → DevoxxGenie → General configurable. prompt_executed fires inside PromptExecutionService after processCommands confirms a real LLM dispatch, so locally-handled commands (/init, /help, /clear, stop toggles, empty prompts) are excluded by construction. model_selected is gated by isInitializationComplete plus a new suppressModelSelectionTracking flag wrapping settingsChanged() and LlmProviderPanel.isUpdatingModelNames() wrapping updateModelNamesComboBox and restoreLastSelectedLanguageModel — preventing false events from provider switches, settings refreshes, and programmatic restores. Includes 8 AnalyticsServiceTest cases (payload allowlist, gating, silent failure, no-PII, client_id persistence) and a regression test LlmProviderPanelTest.isUpdatingModelNames_isTrueDuringUpdateAndFalseAfter for the user-action guard. README, plugin.xml description, plugin.xml change-notes, CHANGELOG, and the General settings help text all explicitly enumerate every field collected and how to opt out. Closes task-206. TASK-207 (GenieBuilder backend + admin UI to surface prompt_executed and add an app_name filter) is a hard dependency for end-to-end dashboard visibility but does not block this PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Six pre-existing test failures in ExoChatModelFactoryTest and ExoModelServiceTest were caused by drift between the test mocks and production code: getModels() now first checks for downloaded models, and ensureInstance() now calls waitForInstanceReady() which requires additional mocked HTTP responses. The mocks were not updated to match. Adds ExoTestAssumptions.isExoServerRunning() which performs a 300ms TCP probe to localhost:52415 and gates the four affected ExoChatModelFactoryTest cases and two affected ExoModelServiceTest cases via Assumptions.assumeTrue. Tests still execute against a real Exo server when one is available, and skip cleanly otherwise — making the build green on machines and CI runners without Exo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds .kotlin/ to .gitignore (Kotlin daemon writes session files there during builds; should never be committed). Imports four Claude Code skills from the GenieBuilder project to support the task workflow used here: start-task, git-commit-push-pr, close-task-commit-push-pr, and review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
c652f69 to
86e686e
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
https://delicate-morning-ff55.devoxx.workers.dev) and segments DevoxxGenie traffic viaapp_name=devoxxgenie-intellij. Six fields total:client_id(UUID),session_id(10-digit per-launch),app_version,ide_version,provider_id,model_name. Never sent: prompt text, response text, conversation history, file content, file paths, project names, git remotes, API keys, credentials, token counts, cost data.analyticsNoticeShownflag (race-safe across concurrent project openings) ensures the first-launch notification appears exactly once per install;analyticsNoticeAcknowledgedgates emission until the user explicitly accepts;analyticsEnableddrives the opt-out checkbox under a new Settings → DevoxxGenie → General configurable. Disclosure is duplicated in the README, plugin.xml description (Marketplace), plugin.xml change-notes, CHANGELOG, and the General settings help text.prompt_executedfires insidePromptExecutionService.executePromptafterprocessCommandsconfirms a real LLM dispatch — locally-handled commands (/init,/help,/clear, stop toggles, empty prompts) are excluded by construction.model_selectedis gated by three guards (isInitializationComplete, newsuppressModelSelectionTrackingwrappingsettingsChanged, and newLlmProviderPanel.isUpdatingModelNames()wrappingupdateModelNamesComboBoxandrestoreLastSelectedLanguageModel) so provider switches, settings refreshes, and programmatic restores no longer emit false events.TRACKED_EVENTSallowlist +app_namefilter + admin UI) is a hard dependency for end-to-end dashboard visibility but does not block this PR — events will accumulate in the raw GA4 stream from merge time forward.localhost:52415, and adds.kotlin/to.gitignore.Test plan
./gradlew test— full suite green (was 6 failing; 8 new analytics tests + 1 regression test passing)AnalyticsServiceTestcovers payload allowlist, 10-digit session id, UUID client id persistence, opt-out gating, notice-acknowledgement gating, missing provider/model gating, silent network failure, no-PII guaranteeLlmProviderPanelTest.isUpdatingModelNames_isTrueDuringUpdateAndFalseAfterregression for the user-action guard./gradlew buildPlugin, side-load the ZIP, confirm first-launch consent balloon appears with both inline actions, verify[Disable]synchronously sets the opt-out flag, run a chat and confirm one event is emitted (and zero events fire when opted out or before consent)model_selectedis not emitted by the programmatic repopulation, only by an explicit user pick afterwardsG-VHHFZ5TRG2) filtered byapp_name=devoxxgenie-intellijthat events reach the raw stream🤖 Generated with Claude Code